package com.repair.auth.service.impl;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.hutool.core.util.RandomUtil;
import com.repair.api.user.request.UserLoginStreamRequest;
import com.repair.api.user.request.UserQueryRequest;
import com.repair.api.user.request.UserRegisterRequest;
import com.repair.api.user.response.UserOperateResponse;
import com.repair.api.user.response.UserQueryResponse;
import com.repair.api.user.response.data.UserInfo;
import com.repair.api.user.service.UserFacadeService;
import com.repair.api.user.service.UserOperateStreamFacadeService;
import com.repair.auth.exception.AuthErrorCode;
import com.repair.auth.exception.AuthException;
import com.repair.auth.pojo.dto.UserRegisterOrLoginParam;
import com.repair.auth.pojo.vo.LoginVO;
import com.repair.auth.service.AuthService;
import com.repair.base.exception.BizErrorCode;
import com.repair.base.exception.SystemErrorCode;
import com.repair.base.exception.SystemException;
import com.repair.limiter.utils.SmsRateLimiter;
import com.repair.satoken.utils.StpKit;
import com.repair.sms.service.SmsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

import static com.repair.auth.constant.AuthConstant.CAPTCHA_PREFIX;

/**
 * @author chunyu
 * @since 2024/9/18,17:33
 */
@Slf4j
@Service
public class AuthServiceImpl implements AuthService {

    private static final long LOGIN_EXPIRATION_TIME = 7 * 24 * 60 * 60;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private SmsService smsService;

    @Autowired
    private SmsRateLimiter smsRateLimiter;

    @Qualifier("stringRedisTemplate")
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @DubboReference
    private UserFacadeService userFacadeService;


    @DubboReference
    private UserOperateStreamFacadeService userOperateStreamFacadeService;

    @Override
    public void generateAndSendCaptcha(String telephone) {
        // 1. 限制60s内只能发送一次
        if (!smsRateLimiter.getRateLimit(telephone, 1, 60)) {
            // 如果拿不到许可  也就是60s内发送过
            throw new AuthException(AuthErrorCode.CAPTCHA_TOO_MANY_REQUESTS);
        }
        // 2. 生成验证码 hutool
        String captcha = RandomUtil.randomNumbers(4);
        // 3. 将验证码存到redis 15分钟过期
        redisTemplate.opsForValue().set(CAPTCHA_PREFIX + telephone, captcha, 15, TimeUnit.MINUTES);
        // 4. 异步调用第三方发送短信
        sendCaptchaAsync(telephone, captcha);
    }

    @Override
    public LoginVO loginOrRegister(UserRegisterOrLoginParam userRegisterOrLoginParam) {
        // 1. 验证验证码 redis get
        // 这里是一种fail-fast思想  就是将失败代码提前 可以优化if-else 也可以一定程度提高性能
        String phoneNum = userRegisterOrLoginParam.getTelephone();
        if (!userRegisterOrLoginParam.getCaptcha().equals("8888")) {
            if (phoneNum == null || phoneNum.isEmpty() || !userRegisterOrLoginParam.getCaptcha().equals(stringRedisTemplate.opsForValue().get(CAPTCHA_PREFIX + phoneNum))) {
                throw new AuthException("验证码错误", AuthErrorCode.CAPTCHA_ERROR);
            }
        }

        // 2. 判断注册还是登录  根据手机号查 rpc
        // 封装统一入参
        UserQueryRequest userQueryRequest = new UserQueryRequest(phoneNum);
        UserQueryResponse<UserInfo> userQueryResponse = userFacadeService.query(userQueryRequest);
        // 2.1 注册 rpc调用 注册 生成昵称
        if (userQueryResponse.getData() == null) {
            UserRegisterRequest userRegisterRequest = new UserRegisterRequest(phoneNum);
            // 2.1.1 生成昵称
            // 2.1.2 记录操作流水
            UserOperateResponse userRegisterResponse = userFacadeService.register(userRegisterRequest);
            if (!userRegisterResponse.getSuccess()) {
                throw new SystemException(SystemErrorCode.UNKNOWN_ERROR);
            }
            userQueryResponse = userFacadeService.query(userQueryRequest);
            UserInfo userInfo = userQueryResponse.getData();

            // 2.1.3 设置token
            // https://sa-token.cc/doc.html#/up/remember-me?id=%e8%ae%b0%e4%bd%8f%e6%88%91-%e6%a8%a1%e5%bc%8f
            StpKit.USER.login(userInfo.getId(), new SaLoginModel().setIsLastingCookie(true)        // 是否为持久Cookie（临时Cookie在浏览器关闭时会自动删除，持久Cookie在重新打开后依然存在）
                    .setTimeout(LOGIN_EXPIRATION_TIME)    // 指定此次登录token的有效期, 单位:秒 （如未指定，自动取全局配置的 timeout 值）
                    .setIsWriteHeader(false)         // 是否在登录后将 Token 写入到响应头
            );
            // 2.1.4 设置session
            StpKit.USER.getSession().set(userInfo.getId().toString(), userInfo);
            // 优化到类里构造方法
            //loginVO.setId(userInfo.getId());
            //loginVO.setToken(StpUtil.getTokenValue());
            //loginVO.setExpirationTime(StpUtil.getTokenActiveTimeout());
            return new LoginVO(userInfo);
        }
        // 2.2 登录
        Long id = userQueryResponse.getData().getId();
        SaLoginModel saLoginModel = new SaLoginModel().setIsLastingCookie(true).setTimeout(LOGIN_EXPIRATION_TIME);
        StpKit.USER.login(id, saLoginModel);
        // 2.2.1 流水
        UserInfo userInfo = userQueryResponse.getData();
        UserLoginStreamRequest<UserInfo> userLoginStreamRequest = new UserLoginStreamRequest<>();
        userLoginStreamRequest.setData(userInfo);
        UserOperateResponse userOperateResponse = userFacadeService.loginStream(userLoginStreamRequest);
        if (!userOperateResponse.getSuccess()) {
            throw new SystemException(BizErrorCode.REMOTE_CALL_RESPONSE_IS_FAILED);
        }
        // 2.2.2 设置token
        // 2.3.3 设置session 自动存redis 这是自动存redis
        StpKit.USER.getSession().set(id.toString(), userInfo);
        return new LoginVO(userInfo);
    }

    //用户注销登录
    @Override
    public void Logout() {
        StpKit.USER.logout();
    }

    // fixme 默认当线程的线程池后续优化
    @Async
    public void sendCaptchaAsync(String telephone, String captcha) {
        boolean success = smsService.sendMessage(telephone, captcha);
        if (!success) {
            log.error("验证码发送失败telephone:{}", telephone);
        }
    }
}
